/**
 *
 */
package com.wolfking.jeesite.sign;

import com.wolfking.jeesite.config.SsoConfig;
import com.wolfking.jeesite.entity.User;
import com.wolfking.jeesite.event.LoginEvent;
import com.wolfking.jeesite.event.AbstractSignEvent;
import com.wolfking.jeesite.exception.SignException;
import com.wolfking.jeesite.mybatis.CurrentLoginUserThreadLocal;
import com.wolfking.jeesite.sso.SsoService;
import com.wolfking.jeesite.util.CookieUtil;
import com.wolfking.jeesite.util.SignEventUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Set;

/**
 * <p>
 *
 * @author wolfking@赵伟伟
 * @mail zww199009@163.com
 * @创作日期 2017年4月19日下午3:05:10
 * @版权 归wolfking所有
 */
@Slf4j
@Aspect
@Configuration
@EnableConfigurationProperties(SsoConfig.class)
public class SignAspect {


    @Autowired
    private SsoConfig ssoConfig;
    @Autowired
    private SsoService ssoService;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Around("@within(sign) || @annotation(sign)")
    public Object signOn(ProceedingJoinPoint pjp, Sign sign) throws Throwable {
        return sign(pjp, sign);
    }

    /**
     * 类上是否有@Controller和@RestController注解
     * <p>
     * 方法和类上是否有@RequestMapping注解
     *
     * @param pjp
     * @param sign
     * @return
     * @throws Throwable
     */
    private Object sign(ProceedingJoinPoint pjp, Sign sign) throws Throwable {
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        try {
            Object[] args = pjp.getArgs();
            if (ssoConfig.isEnabled()) {
                Class<?> clazz = pjp.getTarget().getClass();
                if (clazz.getAnnotation(Controller.class) != null || clazz.getAnnotation(RestController.class) != null) {
                    RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                    if (requestMapping == null)
                        requestMapping = method.getAnnotation(RequestMapping.class);
                    if (requestMapping != null) {
                        log.debug("进行sign登录校验");
                        User user = ssoService.getUser();
                        ServletRequestAttributes attributes = ((ServletRequestAttributes)
                                RequestContextHolder.getRequestAttributes());
                        if (Objects.isNull(attributes)) {
                            throw new SignException();
                        }
                        HttpServletRequest request = attributes.getRequest();
                        HttpServletResponse response = attributes.getResponse();
                        if (Objects.isNull(user)) {
                            user = ssoService.checkOtherUser(request);
                            if (Objects.nonNull(user)) {
                                ssoService.login(user);
                                eventPublisher.publishEvent(SignEventUtil.createSignEvent(request, LoginEvent.class, user.getId()));
                                CookieUtil.setCookie(request, response, "tokenId", user.getTokenId(), 86400 * 10);
                                CookieUtil.setCookie(request, response, "sso_login", "true", 86400 * 10);
                                request.setAttribute("tokenId", user.getTokenId());
                            }
                        }
                        if (Objects.isNull(user)) {
                            if (!sign.loginPage()) {
                                throw new SignException();
                            }
                        } else {
                            log.debug("@sign校验通过");
                            CurrentLoginUserThreadLocal.setLoginUserId(user.getId());
                            Class<?>[] classes = method.getParameterTypes();
                            for (int i = classes.length - 1; i >= 0; i--) {
                                if (User.class.equals(classes[i])) {
                                    Annotation[] parameterAnnotation = parameterAnnotations[i];
                                    boolean flag = false;
                                    for (Annotation annotation : parameterAnnotation) {
                                        if (annotation instanceof LoginUser) {
                                            flag = true;
                                            break;
                                        }
                                    }
                                    if (flag) {
                                        args[i] = user;
                                        break;
                                    }
                                }
                            }
                            if (StringUtils.isNotEmpty(sign.code())) {
                                Set<String> codeSet = ssoService.getUserAuthCodes(user.getId());
                                String code = sign.code();
                                if (!codeSet.contains(code)) {
                                    throw new SignException(code);
                                }
                            }
                            //发送event
                            if (!sign.loginPage()) {
                                for (Class<? extends AbstractSignEvent> claz : sign.event()) {
                                    if (!claz.equals(AbstractSignEvent.class)) {
                                        try {
                                            AbstractSignEvent signEvent = SignEventUtil.createSignEvent(request, claz, user.getId());
                                            if (StringUtils.isNotBlank(sign.eventName())) {
                                                signEvent.setUrl(sign.eventName());
                                            }
                                            eventPublisher.publishEvent(signEvent);
                                        } catch (Exception e) {
                                            log.warn("", e);
                                        }
                                    }
                                }
                            }
                        }
                        return pjp.proceed(args);
                    } else {
                        log.warn("不是合法的@Sign注解使用");
                    }
                } else {
                    log.warn("不是合法的@Sign注解使用");
                }
            } else if (StringUtils.isNotBlank(ssoConfig.getDebugUserName())) {
                User user = ssoService.getUser();
                log.debug("debugUser");
                Class<?>[] classes = method.getParameterTypes();
                for (int i = classes.length - 1; i >= 0; i--) {
                    if (User.class.equals(classes[i])) {
                        Annotation[] parameterAnnotation = parameterAnnotations[i];
                        boolean flag = false;
                        for (Annotation annotation : parameterAnnotation) {
                            if (annotation instanceof LoginUser) {
                                flag = true;
                                break;
                            }
                        }
                        if (flag) {
                            args[i] = user;
                            break;
                        }
                    }
                }
            }
            return pjp.proceed(args);
        } finally {
            CurrentLoginUserThreadLocal.removeLoginUserId();
        }
    }
}
